// Copyright (c) 2000 Just Objects B.V. <just@justobjects.nl>
// Distributable under LGPL license. See terms of license at gnu.org.

package nl.justobjects.pushlet.core;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;

/**
 * Generic implementation of ClientAdapter for browser clients.
 *
 * @version $Id: BrowserClientAdapter.java,v 1.4 2003/08/15 08:37:40 justb Exp $
 * @author Just van den Broecke - Just Objects &copy;
 */
abstract class BrowserClientAdapter implements ClientAdapter {

	String head = "<HTML><HEAD><META HTTP-EQUIV=\"Pragma\" CONTENT=\"no-cache\"><META HTTP-EQUIV=\"Expires\" CONTENT=\"Tue, 31 Dec 1997 23:59:59 GMT\"></HEAD>";
	String startBody = "<BODY TEXT=white BGCOLOR=\"";
	String endDocument = "</BODY></HTML>";

	String bgColor = null;
	boolean usePreamble = false;

	static public BrowserClientAdapter createPulletAdapter(HttpServletRequest req, HttpServletResponse rsp, String id) throws IOException {
		return new BrowserPulletClientAdapter(id);
	}

	/** Factory method for to create browser-specific adapter. */
	static public BrowserClientAdapter createPushletAdapter(HttpServletRequest req, HttpServletResponse rsp) throws IOException {
		// Currently only a single adapter type
		// Note (21.aug.2000): removed all multipart MIME stuff for NS
		return new BrowserPushletClientAdapter();
	}

	/** Generic init. */
	final public void init(HttpServletRequest req,
						   HttpServletResponse rsp) throws IOException {

		// The client may have indicated its background color, otherwise
		// default.
		bgColor = req.getParameter("bgColor");
		if (bgColor == null) {
			bgColor = "#0000C0";
		}

		// Content type as HTML
		rsp.setContentType("text/html");

		// SHOULD WE ALWAYS USE THIS ??
		// rsp.setHeader("Connection", "Close");
		String userAgent = req.getHeader("User-Agent");

		// HACK: MSIE on PowerMac requires initial blast to prevent
		// its internal buffering!
		if ((userAgent.indexOf("Mac") > 0) &&
				(userAgent.indexOf("MSIE") > 0)) {
			usePreamble = true;
		}

		initResponse(req, rsp);
	}

	/** Start HTML page in client browser. */
	final public void start() throws IOException {
		start(head + startBody + bgColor + "\">");
	}

	/** Send event as JavaScript to browser. */
	final public void push(Event event) throws IOException {
		send(event2JavaScript(event));
	}

	/** End HTML page in client browser. */
	final public void stop() throws IOException {
		stop(endDocument);
	}

	/* These are to be overloaded in subclasses */
	abstract public void initResponse(HttpServletRequest req, HttpServletResponse rsp) throws IOException;

	abstract public void start(String s) throws IOException;

	abstract public void send(String s) throws IOException;

	abstract public void stop(String s) throws IOException;

	/** Converts the Java Event to a JavaScript function call in browser page. */
	protected String event2JavaScript(Event event) throws IOException {

		// Convert the event to a comma-separated string.
		String jsArgs = "";
		for (Enumeration e = event.getAttributeNames(); e.hasMoreElements();) {
			String name = (String) e.nextElement();
			String value = event.getAttribute(name);
			String nextArgument = (jsArgs.equals("")?"":",") + "'" + name + "'" + ", \"" + value + "\"";
			jsArgs += nextArgument;
		}

		// Construct and return the function call */
		return "<script language=\"JavaScript\">parent.push(" + jsArgs + ");</script>";
	}

	/** Sends continuous page to browser (e.g. MSIE). */
	private static class BrowserPushletClientAdapter extends BrowserClientAdapter {
		ServletOutputStream out = null;

		public void initResponse(HttpServletRequest req, HttpServletResponse rsp) throws IOException {
			out = rsp.getOutputStream();

			// SHOULD WE USE CHUNKED ENCODING FOR HTTP1.1 ???
			if ("HTTP/1.1".equals(req.getProtocol())) {
				// rsp.setHeader("Transfer-Encoding","chunked");
			}

			rsp.setContentType("text/html");
		}

		public void start(String s) throws IOException {
			send(s);

			// This is used to prevent internal buffering in some browsers
			// like M$IE Mac.
			if (usePreamble) {
				Log.debug("Using preamble");
				StringBuffer stringBuffer = new StringBuffer(100);
				for (int i = 0; i < 100; i++) {
					stringBuffer.append("<!-- PREAMBLE -->     ");
				}
				send(stringBuffer.toString());
			}
		}

		public void send(String s) throws IOException {
			out.write(s.getBytes(), 0, s.length());
			out.flush();
		}

		public void stop(String s) throws IOException {
			send(s);
		}
	}

	/** Sends page to browser for one more events. */
	private static class BrowserPulletClientAdapter extends BrowserClientAdapter {
		ServletOutputStream out = null;
		String id = null;

		/** The servlet request to continue (refresh). */
		String continueReq = null;
		String jsRefreshCallback;

		String doc = null;
		HttpServletResponse rsp;

		/** Constructor with unique client request id. */
		BrowserPulletClientAdapter(String theId) {
			id = theId;
		}

		public void initResponse(HttpServletRequest req, HttpServletResponse theRsp) throws IOException {
			// Need to keep to the response (for content length)
			rsp = theRsp;
			if ("HTTP/1.1".equals(req.getProtocol())) {
				rsp.setHeader("Transfer-Encoding", "chunked");
			}
			out = rsp.getOutputStream();

			// Create servlet request for requesting next events (refresh)
			continueReq = req.getRequestURI() + "?id=" + id;

			// Construct the JS callback line to be sent as last line of doc.
			// This will refresh the request using the unique id to determine
			jsRefreshCallback = "\n<script language=\"JavaScript\">document.location.href=\"" + continueReq + "\";\n</script>";
		}

		/** Starts the page. */
		public void start(String s) throws IOException {
			// Do not send until stop(), this way we can set the content
			// length for simple implementation (i.s.o. chunked encoding)
			// for clients.
			send(s);
		}

		/** Send any string to browser. */
		public void send(String s) throws IOException {
			// doc += s;
			out.write(s.getBytes(), 0, s.length());
			out.flush();
		}

		/** Ends the page and appends JS call to refresh. */
		public void stop(String endDoc) throws IOException {
			// System.out.println("continueReq="+continueReq);
			// Create the entire document (TODO use StringBuffer)
			send(jsRefreshCallback + endDoc);

			// We now know the length
			// rsp.setContentLength(doc.length());

			// Send entire document to client.

			// System.out.println("SENDING doc");
		}
	}

}

/*
 * $Log: BrowserClientAdapter.java,v $
 * Revision 1.4  2003/08/15 08:37:40  justb
 * fix/add Copyright+LGPL file headers and footers
 *
 * Revision 1.3  2003/08/12 09:57:05  justb
 * replaced all print statements to Log.*() calls
 *
 * Revision 1.2  2003/05/18 16:15:07  justb
 * support for XML encoded Events
 *
 * Revision 1.1.1.1  2002/09/24 21:02:30  justb
 * import to sourceforge
 *
 * Revision 1.1.1.1  2002/09/20 22:48:17  justb
 * import to SF
 *
 * Revision 1.1.1.1  2002/09/20 14:19:02  justb
 * first import into SF
 *
 * Revision 1.5  2002/04/15 20:42:41  just
 * reformatting and renaming GuardedQueue to BlockingQueue
 *
 * Revision 1.4  2000/12/27 22:39:35  just
 * no message
 *
 * Revision 1.3  2000/10/30 14:15:47  just
 * no message
 *
 *
 */

